/*!
 * # Semantic UI - API
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

(function($, window, document, undefined) {
  "use strict";

  var window =
    typeof window != "undefined" && window.Math == Math
      ? window
      : typeof self != "undefined" && self.Math == Math
        ? self
        : Function("return this")();

  $.api = $.fn.api = function(parameters) {
    var // use window context if none specified
      $allModules = $.isFunction(this) ? $(window) : $(this),
      moduleSelector = $allModules.selector || "",
      time = new Date().getTime(),
      performance = [],
      query = arguments[0],
      methodInvoked = typeof query == "string",
      queryArguments = [].slice.call(arguments, 1),
      returnedValue;

    $allModules.each(function() {
      var settings = $.isPlainObject(parameters)
          ? $.extend(true, {}, $.fn.api.settings, parameters)
          : $.extend({}, $.fn.api.settings),
        // internal aliases
        namespace = settings.namespace,
        metadata = settings.metadata,
        selector = settings.selector,
        error = settings.error,
        className = settings.className,
        // define namespaces for modules
        eventNamespace = "." + namespace,
        moduleNamespace = "module-" + namespace,
        // element that creates request
        $module = $(this),
        $form = $module.closest(selector.form),
        // context used for state
        $context = settings.stateContext ? $(settings.stateContext) : $module,
        // request details
        ajaxSettings,
        requestSettings,
        url,
        data,
        requestStartTime,
        // standard module
        element = this,
        context = $context[0],
        instance = $module.data(moduleNamespace),
        module;

      module = {
        initialize: function() {
          if (!methodInvoked) {
            module.bind.events();
          }
          module.instantiate();
        },

        instantiate: function() {
          module.verbose("Storing instance of module", module);
          instance = module;
          $module.data(moduleNamespace, instance);
        },

        destroy: function() {
          module.verbose("Destroying previous module for", element);
          $module.removeData(moduleNamespace).off(eventNamespace);
        },

        bind: {
          events: function() {
            var triggerEvent = module.get.event();
            if (triggerEvent) {
              module.verbose("Attaching API events to element", triggerEvent);
              $module.on(triggerEvent + eventNamespace, module.event.trigger);
            } else if (settings.on == "now") {
              module.debug("Querying API endpoint immediately");
              module.query();
            }
          }
        },

        decode: {
          json: function(response) {
            if (response !== undefined && typeof response == "string") {
              try {
                response = JSON.parse(response);
              } catch (e) {
                // isnt json string
              }
            }
            return response;
          }
        },

        read: {
          cachedResponse: function(url) {
            var response;
            if (window.Storage === undefined) {
              module.error(error.noStorage);
              return;
            }
            response = sessionStorage.getItem(url);
            module.debug("Using cached response", url, response);
            response = module.decode.json(response);
            return response;
          }
        },
        write: {
          cachedResponse: function(url, response) {
            if (response && response === "") {
              module.debug("Response empty, not caching", response);
              return;
            }
            if (window.Storage === undefined) {
              module.error(error.noStorage);
              return;
            }
            if ($.isPlainObject(response)) {
              response = JSON.stringify(response);
            }
            sessionStorage.setItem(url, response);
            module.verbose("Storing cached response for url", url, response);
          }
        },

        query: function() {
          if (module.is.disabled()) {
            module.debug("Element is disabled API request aborted");
            return;
          }

          if (module.is.loading()) {
            if (settings.interruptRequests) {
              module.debug("Interrupting previous request");
              module.abort();
            } else {
              module.debug(
                "Cancelling request, previous request is still pending"
              );
              return;
            }
          }

          // pass element metadata to url (value, text)
          if (settings.defaultData) {
            $.extend(true, settings.urlData, module.get.defaultData());
          }

          // Add form content
          if (settings.serializeForm) {
            settings.data = module.add.formData(settings.data);
          }

          // call beforesend and get any settings changes
          requestSettings = module.get.settings();

          // check if before send cancelled request
          if (requestSettings === false) {
            module.cancelled = true;
            module.error(error.beforeSend);
            return;
          } else {
            module.cancelled = false;
          }

          // get url
          url = module.get.templatedURL();

          if (!url && !module.is.mocked()) {
            module.error(error.missingURL);
            return;
          }

          // replace variables
          url = module.add.urlData(url);
          // missing url parameters
          if (!url && !module.is.mocked()) {
            return;
          }

          requestSettings.url = settings.base + url;

          // look for jQuery ajax parameters in settings
          ajaxSettings = $.extend(true, {}, settings, {
            type: settings.method || settings.type,
            data: data,
            url: settings.base + url,
            beforeSend: settings.beforeXHR,
            success: function() {},
            failure: function() {},
            complete: function() {}
          });

          module.debug("Querying URL", ajaxSettings.url);
          module.verbose("Using AJAX settings", ajaxSettings);
          if (settings.cache === "local" && module.read.cachedResponse(url)) {
            module.debug("Response returned from local cache");
            module.request = module.create.request();
            module.request.resolveWith(context, [
              module.read.cachedResponse(url)
            ]);
            return;
          }

          if (!settings.throttle) {
            module.debug("Sending request", data, ajaxSettings.method);
            module.send.request();
          } else {
            if (!settings.throttleFirstRequest && !module.timer) {
              module.debug("Sending request", data, ajaxSettings.method);
              module.send.request();
              module.timer = setTimeout(function() {}, settings.throttle);
            } else {
              module.debug("Throttling request", settings.throttle);
              clearTimeout(module.timer);
              module.timer = setTimeout(function() {
                if (module.timer) {
                  delete module.timer;
                }
                module.debug(
                  "Sending throttled request",
                  data,
                  ajaxSettings.method
                );
                module.send.request();
              }, settings.throttle);
            }
          }
        },

        should: {
          removeError: function() {
            return (
              settings.hideError === true ||
              (settings.hideError === "auto" && !module.is.form())
            );
          }
        },

        is: {
          disabled: function() {
            return $module.filter(selector.disabled).length > 0;
          },
          expectingJSON: function() {
            return (
              settings.dataType === "json" || settings.dataType === "jsonp"
            );
          },
          form: function() {
            return $module.is("form") || $context.is("form");
          },
          mocked: function() {
            return (
              settings.mockResponse ||
              settings.mockResponseAsync ||
              settings.response ||
              settings.responseAsync
            );
          },
          input: function() {
            return $module.is("input");
          },
          loading: function() {
            return module.request ? module.request.state() == "pending" : false;
          },
          abortedRequest: function(xhr) {
            if (xhr && xhr.readyState !== undefined && xhr.readyState === 0) {
              module.verbose("XHR request determined to be aborted");
              return true;
            } else {
              module.verbose("XHR request was not aborted");
              return false;
            }
          },
          validResponse: function(response) {
            if (
              !module.is.expectingJSON() ||
              !$.isFunction(settings.successTest)
            ) {
              module.verbose(
                "Response is not JSON, skipping validation",
                settings.successTest,
                response
              );
              return true;
            }
            module.debug(
              "Checking JSON returned success",
              settings.successTest,
              response
            );
            if (settings.successTest(response)) {
              module.debug("Response passed success test", response);
              return true;
            } else {
              module.debug("Response failed success test", response);
              return false;
            }
          }
        },

        was: {
          cancelled: function() {
            return module.cancelled || false;
          },
          succesful: function() {
            return module.request && module.request.state() == "resolved";
          },
          failure: function() {
            return module.request && module.request.state() == "rejected";
          },
          complete: function() {
            return (
              module.request &&
              (module.request.state() == "resolved" ||
                module.request.state() == "rejected")
            );
          }
        },

        add: {
          urlData: function(url, urlData) {
            var requiredVariables, optionalVariables;
            if (url) {
              requiredVariables = url.match(settings.regExp.required);
              optionalVariables = url.match(settings.regExp.optional);
              urlData = urlData || settings.urlData;
              if (requiredVariables) {
                module.debug(
                  "Looking for required URL variables",
                  requiredVariables
                );
                $.each(requiredVariables, function(index, templatedString) {
                  var // allow legacy {$var} style
                    variable =
                      templatedString.indexOf("$") !== -1
                        ? templatedString.substr(2, templatedString.length - 3)
                        : templatedString.substr(1, templatedString.length - 2),
                    value =
                      $.isPlainObject(urlData) &&
                      urlData[variable] !== undefined
                        ? urlData[variable]
                        : $module.data(variable) !== undefined
                          ? $module.data(variable)
                          : $context.data(variable) !== undefined
                            ? $context.data(variable)
                            : urlData[variable];
                  // remove value
                  if (value === undefined) {
                    module.error(error.requiredParameter, variable, url);
                    url = false;
                    return false;
                  } else {
                    module.verbose("Found required variable", variable, value);
                    value = settings.encodeParameters
                      ? module.get.urlEncodedValue(value)
                      : value;
                    url = url.replace(templatedString, value);
                  }
                });
              }
              if (optionalVariables) {
                module.debug(
                  "Looking for optional URL variables",
                  requiredVariables
                );
                $.each(optionalVariables, function(index, templatedString) {
                  var // allow legacy {/$var} style
                    variable =
                      templatedString.indexOf("$") !== -1
                        ? templatedString.substr(3, templatedString.length - 4)
                        : templatedString.substr(2, templatedString.length - 3),
                    value =
                      $.isPlainObject(urlData) &&
                      urlData[variable] !== undefined
                        ? urlData[variable]
                        : $module.data(variable) !== undefined
                          ? $module.data(variable)
                          : $context.data(variable) !== undefined
                            ? $context.data(variable)
                            : urlData[variable];
                  // optional replacement
                  if (value !== undefined) {
                    module.verbose("Optional variable Found", variable, value);
                    url = url.replace(templatedString, value);
                  } else {
                    module.verbose("Optional variable not found", variable);
                    // remove preceding slash if set
                    if (url.indexOf("/" + templatedString) !== -1) {
                      url = url.replace("/" + templatedString, "");
                    } else {
                      url = url.replace(templatedString, "");
                    }
                  }
                });
              }
            }
            return url;
          },
          formData: function(data) {
            var canSerialize = $.fn.serializeObject !== undefined,
              formData = canSerialize
                ? $form.serializeObject()
                : $form.serialize(),
              hasOtherData;
            data = data || settings.data;
            hasOtherData = $.isPlainObject(data);

            if (hasOtherData) {
              if (canSerialize) {
                module.debug(
                  "Extending existing data with form data",
                  data,
                  formData
                );
                data = $.extend(true, {}, data, formData);
              } else {
                module.error(error.missingSerialize);
                module.debug(
                  "Cant extend data. Replacing data with form data",
                  data,
                  formData
                );
                data = formData;
              }
            } else {
              module.debug("Adding form data", formData);
              data = formData;
            }
            return data;
          }
        },

        send: {
          request: function() {
            module.set.loading();
            module.request = module.create.request();
            if (module.is.mocked()) {
              module.mockedXHR = module.create.mockedXHR();
            } else {
              module.xhr = module.create.xhr();
            }
            settings.onRequest.call(context, module.request, module.xhr);
          }
        },

        event: {
          trigger: function(event) {
            module.query();
            if (event.type == "submit" || event.type == "click") {
              event.preventDefault();
            }
          },
          xhr: {
            always: function() {
              // nothing special
            },
            done: function(response, textStatus, xhr) {
              var context = this,
                elapsedTime = new Date().getTime() - requestStartTime,
                timeLeft = settings.loadingDuration - elapsedTime,
                translatedResponse = $.isFunction(settings.onResponse)
                  ? module.is.expectingJSON()
                    ? settings.onResponse.call(
                        context,
                        $.extend(true, {}, response)
                      )
                    : settings.onResponse.call(context, response)
                  : false;
              timeLeft = timeLeft > 0 ? timeLeft : 0;
              if (translatedResponse) {
                module.debug(
                  "Modified API response in onResponse callback",
                  settings.onResponse,
                  translatedResponse,
                  response
                );
                response = translatedResponse;
              }
              if (timeLeft > 0) {
                module.debug(
                  "Response completed early delaying state change by",
                  timeLeft
                );
              }
              setTimeout(function() {
                if (module.is.validResponse(response)) {
                  module.request.resolveWith(context, [response, xhr]);
                } else {
                  module.request.rejectWith(context, [xhr, "invalid"]);
                }
              }, timeLeft);
            },
            fail: function(xhr, status, httpMessage) {
              var context = this,
                elapsedTime = new Date().getTime() - requestStartTime,
                timeLeft = settings.loadingDuration - elapsedTime;
              timeLeft = timeLeft > 0 ? timeLeft : 0;
              if (timeLeft > 0) {
                module.debug(
                  "Response completed early delaying state change by",
                  timeLeft
                );
              }
              setTimeout(function() {
                if (module.is.abortedRequest(xhr)) {
                  module.request.rejectWith(context, [
                    xhr,
                    "aborted",
                    httpMessage
                  ]);
                } else {
                  module.request.rejectWith(context, [
                    xhr,
                    "error",
                    status,
                    httpMessage
                  ]);
                }
              }, timeLeft);
            }
          },
          request: {
            done: function(response, xhr) {
              module.debug("Successful API Response", response);
              if (settings.cache === "local" && url) {
                module.write.cachedResponse(url, response);
                module.debug("Saving server response locally", module.cache);
              }
              settings.onSuccess.call(context, response, $module, xhr);
            },
            complete: function(firstParameter, secondParameter) {
              var xhr, response;
              // have to guess callback parameters based on request success
              if (module.was.succesful()) {
                response = firstParameter;
                xhr = secondParameter;
              } else {
                xhr = firstParameter;
                response = module.get.responseFromXHR(xhr);
              }
              module.remove.loading();
              settings.onComplete.call(context, response, $module, xhr);
            },
            fail: function(xhr, status, httpMessage) {
              var // pull response from xhr if available
                response = module.get.responseFromXHR(xhr),
                errorMessage = module.get.errorFromRequest(
                  response,
                  status,
                  httpMessage
                );
              if (status == "aborted") {
                module.debug(
                  "XHR Aborted (Most likely caused by page navigation or CORS Policy)",
                  status,
                  httpMessage
                );
                settings.onAbort.call(context, status, $module, xhr);
                return true;
              } else if (status == "invalid") {
                module.debug(
                  "JSON did not pass success test. A server-side error has most likely occurred",
                  response
                );
              } else if (status == "error") {
                if (xhr !== undefined) {
                  module.debug(
                    "XHR produced a server error",
                    status,
                    httpMessage
                  );
                  // make sure we have an error to display to console
                  if (
                    xhr.status != 200 &&
                    httpMessage !== undefined &&
                    httpMessage !== ""
                  ) {
                    module.error(
                      error.statusMessage + httpMessage,
                      ajaxSettings.url
                    );
                  }
                  settings.onError.call(context, errorMessage, $module, xhr);
                }
              }

              if (settings.errorDuration && status !== "aborted") {
                module.debug("Adding error state");
                module.set.error();
                if (module.should.removeError()) {
                  setTimeout(module.remove.error, settings.errorDuration);
                }
              }
              module.debug("API Request failed", errorMessage, xhr);
              settings.onFailure.call(context, response, $module, xhr);
            }
          }
        },

        create: {
          request: function() {
            // api request promise
            return $.Deferred()
              .always(module.event.request.complete)
              .done(module.event.request.done)
              .fail(module.event.request.fail);
          },

          mockedXHR: function() {
            var // xhr does not simulate these properties of xhr but must return them
              textStatus = false,
              status = false,
              httpMessage = false,
              responder = settings.mockResponse || settings.response,
              asyncResponder =
                settings.mockResponseAsync || settings.responseAsync,
              asyncCallback,
              response,
              mockedXHR;

            mockedXHR = $.Deferred()
              .always(module.event.xhr.complete)
              .done(module.event.xhr.done)
              .fail(module.event.xhr.fail);

            if (responder) {
              if ($.isFunction(responder)) {
                module.debug("Using specified synchronous callback", responder);
                response = responder.call(context, requestSettings);
              } else {
                module.debug("Using settings specified response", responder);
                response = responder;
              }
              // simulating response
              mockedXHR.resolveWith(context, [
                response,
                textStatus,
                { responseText: response }
              ]);
            } else if ($.isFunction(asyncResponder)) {
              asyncCallback = function(response) {
                module.debug("Async callback returned response", response);

                if (response) {
                  mockedXHR.resolveWith(context, [
                    response,
                    textStatus,
                    { responseText: response }
                  ]);
                } else {
                  mockedXHR.rejectWith(context, [
                    { responseText: response },
                    status,
                    httpMessage
                  ]);
                }
              };
              module.debug(
                "Using specified async response callback",
                asyncResponder
              );
              asyncResponder.call(context, requestSettings, asyncCallback);
            }
            return mockedXHR;
          },

          xhr: function() {
            var xhr;
            // ajax request promise
            xhr = $.ajax(ajaxSettings)
              .always(module.event.xhr.always)
              .done(module.event.xhr.done)
              .fail(module.event.xhr.fail);
            module.verbose("Created server request", xhr, ajaxSettings);
            return xhr;
          }
        },

        set: {
          error: function() {
            module.verbose("Adding error state to element", $context);
            $context.addClass(className.error);
          },
          loading: function() {
            module.verbose("Adding loading state to element", $context);
            $context.addClass(className.loading);
            requestStartTime = new Date().getTime();
          }
        },

        remove: {
          error: function() {
            module.verbose("Removing error state from element", $context);
            $context.removeClass(className.error);
          },
          loading: function() {
            module.verbose("Removing loading state from element", $context);
            $context.removeClass(className.loading);
          }
        },

        get: {
          responseFromXHR: function(xhr) {
            return $.isPlainObject(xhr)
              ? module.is.expectingJSON()
                ? module.decode.json(xhr.responseText)
                : xhr.responseText
              : false;
          },
          errorFromRequest: function(response, status, httpMessage) {
            return $.isPlainObject(response) && response.error !== undefined
              ? response.error // use json error message
              : settings.error[status] !== undefined // use server error message
                ? settings.error[status]
                : httpMessage;
          },
          request: function() {
            return module.request || false;
          },
          xhr: function() {
            return module.xhr || false;
          },
          settings: function() {
            var runSettings;
            runSettings = settings.beforeSend.call(context, settings);
            if (runSettings) {
              if (runSettings.success !== undefined) {
                module.debug("Legacy success callback detected", runSettings);
                module.error(error.legacyParameters, runSettings.success);
                runSettings.onSuccess = runSettings.success;
              }
              if (runSettings.failure !== undefined) {
                module.debug("Legacy failure callback detected", runSettings);
                module.error(error.legacyParameters, runSettings.failure);
                runSettings.onFailure = runSettings.failure;
              }
              if (runSettings.complete !== undefined) {
                module.debug("Legacy complete callback detected", runSettings);
                module.error(error.legacyParameters, runSettings.complete);
                runSettings.onComplete = runSettings.complete;
              }
            }
            if (runSettings === undefined) {
              module.error(error.noReturnedValue);
            }
            if (runSettings === false) {
              return runSettings;
            }
            return runSettings !== undefined
              ? $.extend(true, {}, runSettings)
              : $.extend(true, {}, settings);
          },
          urlEncodedValue: function(value) {
            var decodedValue = window.decodeURIComponent(value),
              encodedValue = window.encodeURIComponent(value),
              alreadyEncoded = decodedValue !== value;
            if (alreadyEncoded) {
              module.debug(
                "URL value is already encoded, avoiding double encoding",
                value
              );
              return value;
            }
            module.verbose(
              "Encoding value using encodeURIComponent",
              value,
              encodedValue
            );
            return encodedValue;
          },
          defaultData: function() {
            var data = {};
            if (!$.isWindow(element)) {
              if (module.is.input()) {
                data.value = $module.val();
              } else if (module.is.form()) {
              } else {
                data.text = $module.text();
              }
            }
            return data;
          },
          event: function() {
            if ($.isWindow(element) || settings.on == "now") {
              module.debug("API called without element, no events attached");
              return false;
            } else if (settings.on == "auto") {
              if ($module.is("input")) {
                return element.oninput !== undefined
                  ? "input"
                  : element.onpropertychange !== undefined
                    ? "propertychange"
                    : "keyup";
              } else if ($module.is("form")) {
                return "submit";
              } else {
                return "click";
              }
            } else {
              return settings.on;
            }
          },
          templatedURL: function(action) {
            action =
              action ||
              $module.data(metadata.action) ||
              settings.action ||
              false;
            url = $module.data(metadata.url) || settings.url || false;
            if (url) {
              module.debug("Using specified url", url);
              return url;
            }
            if (action) {
              module.debug("Looking up url for action", action, settings.api);
              if (settings.api[action] === undefined && !module.is.mocked()) {
                module.error(
                  error.missingAction,
                  settings.action,
                  settings.api
                );
                return;
              }
              url = settings.api[action];
            } else if (module.is.form()) {
              url = $module.attr("action") || $context.attr("action") || false;
              module.debug(
                "No url or action specified, defaulting to form action",
                url
              );
            }
            return url;
          }
        },

        abort: function() {
          var xhr = module.get.xhr();
          if (xhr && xhr.state() !== "resolved") {
            module.debug("Cancelling API request");
            xhr.abort();
          }
        },

        // reset state
        reset: function() {
          module.remove.error();
          module.remove.loading();
        },

        setting: function(name, value) {
          module.debug("Changing setting", name, value);
          if ($.isPlainObject(name)) {
            $.extend(true, settings, name);
          } else if (value !== undefined) {
            if ($.isPlainObject(settings[name])) {
              $.extend(true, settings[name], value);
            } else {
              settings[name] = value;
            }
          } else {
            return settings[name];
          }
        },
        internal: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, module, name);
          } else if (value !== undefined) {
            module[name] = value;
          } else {
            return module[name];
          }
        },
        debug: function() {
          if (!settings.silent && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.debug = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.debug.apply(console, arguments);
            }
          }
        },
        verbose: function() {
          if (!settings.silent && settings.verbose && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.verbose = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.verbose.apply(console, arguments);
            }
          }
        },
        error: function() {
          if (!settings.silent) {
            module.error = Function.prototype.bind.call(
              console.error,
              console,
              settings.name + ":"
            );
            module.error.apply(console, arguments);
          }
        },
        performance: {
          log: function(message) {
            var currentTime, executionTime, previousTime;
            if (settings.performance) {
              currentTime = new Date().getTime();
              previousTime = time || currentTime;
              executionTime = currentTime - previousTime;
              time = currentTime;
              performance.push({
                Name: message[0],
                Arguments: [].slice.call(message, 1) || "",
                //'Element'        : element,
                "Execution Time": executionTime
              });
            }
            clearTimeout(module.performance.timer);
            module.performance.timer = setTimeout(
              module.performance.display,
              500
            );
          },
          display: function() {
            var title = settings.name + ":",
              totalTime = 0;
            time = false;
            clearTimeout(module.performance.timer);
            $.each(performance, function(index, data) {
              totalTime += data["Execution Time"];
            });
            title += " " + totalTime + "ms";
            if (moduleSelector) {
              title += " '" + moduleSelector + "'";
            }
            if (
              (console.group !== undefined || console.table !== undefined) &&
              performance.length > 0
            ) {
              console.groupCollapsed(title);
              if (console.table) {
                console.table(performance);
              } else {
                $.each(performance, function(index, data) {
                  console.log(
                    data["Name"] + ": " + data["Execution Time"] + "ms"
                  );
                });
              }
              console.groupEnd();
            }
            performance = [];
          }
        },
        invoke: function(query, passedArguments, context) {
          var object = instance,
            maxDepth,
            found,
            response;
          passedArguments = passedArguments || queryArguments;
          context = element || context;
          if (typeof query == "string" && object !== undefined) {
            query = query.split(/[\. ]/);
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              var camelCaseValue =
                depth != maxDepth
                  ? value +
                    query[depth + 1].charAt(0).toUpperCase() +
                    query[depth + 1].slice(1)
                  : query;
              if (
                $.isPlainObject(object[camelCaseValue]) &&
                depth != maxDepth
              ) {
                object = object[camelCaseValue];
              } else if (object[camelCaseValue] !== undefined) {
                found = object[camelCaseValue];
                return false;
              } else if ($.isPlainObject(object[value]) && depth != maxDepth) {
                object = object[value];
              } else if (object[value] !== undefined) {
                found = object[value];
                return false;
              } else {
                module.error(error.method, query);
                return false;
              }
            });
          }
          if ($.isFunction(found)) {
            response = found.apply(context, passedArguments);
          } else if (found !== undefined) {
            response = found;
          }
          if ($.isArray(returnedValue)) {
            returnedValue.push(response);
          } else if (returnedValue !== undefined) {
            returnedValue = [returnedValue, response];
          } else if (response !== undefined) {
            returnedValue = response;
          }
          return found;
        }
      };

      if (methodInvoked) {
        if (instance === undefined) {
          module.initialize();
        }
        module.invoke(query);
      } else {
        if (instance !== undefined) {
          instance.invoke("destroy");
        }
        module.initialize();
      }
    });

    return returnedValue !== undefined ? returnedValue : this;
  };

  $.api.settings = {
    name: "API",
    namespace: "api",

    debug: false,
    verbose: false,
    performance: true,

    // object containing all templates endpoints
    api: {},

    // whether to cache responses
    cache: true,

    // whether new requests should abort previous requests
    interruptRequests: true,

    // event binding
    on: "auto",

    // context for applying state classes
    stateContext: false,

    // duration for loading state
    loadingDuration: 0,

    // whether to hide errors after a period of time
    hideError: "auto",

    // duration for error state
    errorDuration: 2000,

    // whether parameters should be encoded with encodeURIComponent
    encodeParameters: true,

    // API action to use
    action: false,

    // templated URL to use
    url: false,

    // base URL to apply to all endpoints
    base: "",

    // data that will
    urlData: {},

    // whether to add default data to url data
    defaultData: true,

    // whether to serialize closest form
    serializeForm: false,

    // how long to wait before request should occur
    throttle: 0,

    // whether to throttle first request or only repeated
    throttleFirstRequest: true,

    // standard ajax settings
    method: "get",
    data: {},
    dataType: "json",

    // mock response
    mockResponse: false,
    mockResponseAsync: false,

    // aliases for mock
    response: false,
    responseAsync: false,

    // callbacks before request
    beforeSend: function(settings) {
      return settings;
    },
    beforeXHR: function(xhr) {},
    onRequest: function(promise, xhr) {},

    // after request
    onResponse: false, // function(response) { },

    // response was successful, if JSON passed validation
    onSuccess: function(response, $module) {},

    // request finished without aborting
    onComplete: function(response, $module) {},

    // failed JSON success test
    onFailure: function(response, $module) {},

    // server error
    onError: function(errorMessage, $module) {},

    // request aborted
    onAbort: function(errorMessage, $module) {},

    successTest: false,

    // errors
    error: {
      beforeSend: "The before send function has aborted the request",
      error: "There was an error with your request",
      exitConditions: "API Request Aborted. Exit conditions met",
      JSONParse: "JSON could not be parsed during error handling",
      legacyParameters: "You are using legacy API success callback names",
      method: "The method you called is not defined",
      missingAction: "API action used but no url was defined",
      missingSerialize:
        "jquery-serialize-object is required to add form data to an existing data object",
      missingURL: "No URL specified for api event",
      noReturnedValue:
        "The beforeSend callback must return a settings object, beforeSend ignored.",
      noStorage: "Caching responses locally requires session storage",
      parseError: "There was an error parsing your request",
      requiredParameter: "Missing a required URL parameter: ",
      statusMessage: "Server gave an error: ",
      timeout: "Your request timed out"
    },

    regExp: {
      required: /\{\$*[A-z0-9]+\}/g,
      optional: /\{\/\$*[A-z0-9]+\}/g
    },

    className: {
      loading: "loading",
      error: "error"
    },

    selector: {
      disabled: ".disabled",
      form: "form"
    },

    metadata: {
      action: "action",
      url: "url"
    }
  };
})(jQuery, window, document);
